#
# messkit.buffer
#
# Copyright 2006 Helsinki Institute for Information Technology (HIIT)
# and the authors.
#
# Authors: Ken Rimey <rimey@hiit.fi>
#

# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
Scatter-gather buffer
"""

from thread import allocate_lock # PyS60 doesn't come with threading installed.

class Buffer:
    def __init__(self):
        self.buf = []           # List of strings.
        self.start = 0          # Bytes to skip in first string.
        self.size = 0           # Total bytes excluding those skipped.  Public.
        self.mutex = allocate_lock()

    def append(self, data):
        if not isinstance(data, str):
            raise TypeError

        self.mutex.acquire()
        try:
            self.buf.append(data)
            self.size += len(data)
        finally:
            self.mutex.release()

    def peek(self, size, start=0):
        self.mutex.acquire()
        try:
            return self._peek(size, start)
        finally:
            self.mutex.release()

    def skip(self, size):
        self.mutex.acquire()
        try:
            self._skip(size)
        finally:
            self.mutex.release()

    def read(self, size):
        self.mutex.acquire()
        try:
            return self._read(size)
        finally:
            self.mutex.release()

    def _peek(self, size, start):
        # Set up a sliding window.
        start += self.start
        end = start + size

        # Gather the fragments seen through the window.
        result = []
        for s in self.buf:
            n = len(s)
            result.append(s[start:end])
            end -= n
            if end <= 0:
                break
            start = max(start - n, 0)

        # Assemble them.
        return ''.join(result)

    def _skip(self, size):
        buf = self.buf

        # Handle discarding of the entire contents as a special case.
        if size >= self.size:
            del buf[:]
            self.start = 0
            self.size = 0
            return

        # Update the stored size in advance.
        self.size -= size

        # Include the offset in the bytes to be skipped so it can be ignored.
        size += self.start

        # Possibly discard some number of buffers whole.
        k = 0
        for s in buf:
            n = len(s)
            if size < n:
                break
            size -= n
            k += 1
        del buf[:k]

        # Set the offset to handle the remainder.
        self.start = size

    def _read(self, size):
        buf = self.buf
        if not buf:
            return ''

        # Determine the number of bytes that will actually be read.
        size = min(size, self.size)

        # Update the stored size in advance.
        self.size -= size

        # Grab the result from the first buffer if possible.
        s = buf[0]
        start = self.start
        if size < len(s) - start:
            s = s[start : start + size]
            self.start += size
            return s

        # Zero the offset to simplify things.
        if start > 0:
            buf[0] = s[start:]
            self.start = 0

        # Determine the number of buffers to take whole.
        k = 0
        for s in buf:
            n = len(s)
            if size < n:
                break
            size -= n
            k += 1

        # Take them.
        result = buf[:k]
        del buf[:k]

        # Chop any remainder off the next buffer.
        if size > 0:
            s = buf[0]
            result.append(s[:size])
            self.start = size

        # Assemble the result.
        return ''.join(result)

if __name__ == '__main__':
    from random import randint

    N = 10000
    K = 5

    text = 'abcdefghijklmnopqrstuvwxyz'
    T = len(text)
    text = text * 100

    i = j = 0
    b = Buffer()
    for repeat in xrange(N):
        k = j % T
        n = randint(0, K)
        b.append(text[k:k+n])
        j = j + n

        k = i % T
        n = j - i
        s = text[k:k+n]
        assert b.peek(len(s) + 1) == s

        p = randint(0, K)
        q = randint(0, K)
        assert b.peek(q, p) == s[p:p+q]

        n = randint(0, K + 1)
        if repeat % 2:
            b.skip(n)
        else:
            t = b.read(n)
            assert t == s[:n]
        i = min(i + n, j)
        assert b.size == j - i

    print "OK"
